Conversation
MAT-614. 신규 BE API (/api/admin/concept/graph/*) 위에 시트형 관리자 페이지 /concept-graph 를 추가한다. 기존 /concept-tags 레거시 페이지는 그대로 유지. - /concept-graph/node — 개념 노드 시트 (필터·정렬·페이지네이션 + CRUD, payload JSON 검증) - /concept-graph/edge — 개념 엣지 시트 (NodeSearchSelect 신설로 from/to 노드 검색 선택) - /concept-graph/action-edge — 액션 그래프 pivot 시트, 셀 클릭 시 우측 사이드 패널에서 다중 선택 후 일괄 PUT - /concept-graph/types — 노드/엣지/액션엣지 타입 코드 CRUD - 공통 시트 컴포넌트(SheetTable, PaginationControls, SearchFilterBar, RowActions) + ConceptGraphTabs - 24개 openapi-react-query wrapper, useInvalidate 5개 메서드 확장, GNB "개념 그래프" 메뉴 추가 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
AddActionRowModal 의 액션 노드 선택을 nodeType 으로 제한할 수 있도록 한다. - NodeSearchSelect 에 nodeTypeId / disabled prop 추가 → getSheetNode 의 nodeTypeId 쿼리로 후보를 좁힘 - AddActionRowModal 에 "액션 노드 타입" 드롭다운 추가, code === 'ACTION' 매칭이 있으면 자동 prefill, 없으면 사용자가 직접 선택 - 타입 변경 시 기존 액션 노드 선택 초기화 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
액션 그래프 맥락에서 actionNode 는 항상 ACTION 타입이어야 하므로 사용자가 타입을 직접 선택할 필요가 없다. 이전 커밋의 dropdown 을 제거하고 ACTION 코드의 nodeType 을 NodeSearchSelect 에 자동 전달한다. - ACTION nodeType 이 없으면 amber 안내 배너 + 등록 버튼 disabled - 노드 후보는 항상 ACTION 타입으로만 필터링 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
운영자에게 ID 노출이 불필요하다는 피드백을 반영해 모든 시트의 ID 컬럼을
숨기고, 삭제 확인 모달의 ID 표기도 함께 제거한다.
- /concept-graph/node, edge, types 의 SheetTable 컬럼 정의에서 id 항목 삭제
- /concept-graph/edge 삭제 확인 본문에서 "#{id}" 표기 제거
(from → to 노드 이름만으로 식별 가능)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BE 데이터의 실제 코드 값이 'Action' (capitalized) 이라 'ACTION' (all caps) 으로 검색하면 매칭되지 않아 amber 경고 배너가 노출되던 이슈 수정. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BE 가 ConceptNode.description 을 plain text 에서 TipTap JSON 에디터 콘텐츠 로 전환함에 따라 FE 도 problem.content 와 동일한 패턴을 적용한다. - EditConceptNodeModal: 설명 입력을 Input 에서 EditorField 로 교체 (problemContent 와 동일한 react-hook-form Controller 기반) - 빈 doc 저장 시 BE 로 undefined 전송, plain text 레거시 데이터는 parseEditorContent 가 fallback 처리 - /concept-graph/node 시트 description 컬럼은 InlineProblemViewer (maxLine=2) 로 인라인 렌더, KaTeX 수식 자동 렌더 + 라인 클램핑 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GNB 에서 "개념 그래프" 를 문제관리/Q&A 와 동등한 최상위 섹션으로 승격하고 4개 NavItem (개념 노드 / 개념 그래프 / 액션 그래프 / 타입 관리) 으로 분리한다. 라우트 내부의 ConceptGraphTabs 는 GNB sub-menu 와 역할이 중복되어 제거. - "개념 엣지" → "개념 그래프" 라벨 변경 (개념 노드 간 관계가 곧 개념 그래프이므로 도메인 명칭에 부합) - 노드 시트 "타입" 컬럼 폭 120px 로 고정 + 폰트 text-xs 로 축소, Tag 컴포넌트 대신 가벼운 inline pill 사용 - ConceptGraphTabs 컴포넌트 / 4개 라우트의 import + 렌더 + 배럴 export 모두 정리 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Action 코드의 nodeType 이 선택되면 raw JSON textarea 대신 "Payload — Example" / "Payload — Pointing Example" 두 개의 TipTap 에디터 필드를 노출한다. 저장 시 두 값은 payload 객체의 같은 이름 키로 들어가고, 불러올 때도 동일한 키에서 읽어온다. - 빈 doc 인 필드는 payload 에서 제외 (둘 다 비어있으면 payload 자체를 undefined 로 전송) - 다른 nodeType 은 기존 raw JSON textarea 그대로 유지 - 타입을 Action 으로 바꾸는 즉시 UI 가 두 에디터로 전환 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
에디터 필드가 추가되면서 560px 폭은 비좁아 860px 로 확장한다. 또한 Action 타입 payload 라벨을 도메인 용어에 맞게 한국어로 표시한다. - w-[560px] → w-[860px] - "Payload — Example" → "예시" - "Payload — Pointing Example" → "포인팅 예시" Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 시트 payload 컬럼에서 nodeType.code === 'Action' 인 행은 raw JSON truncate 대신 InlineProblemViewer 로 예시/포인팅 예시를 한 줄씩 렌더 (KaTeX 자동 렌더 + 라인 클램핑) - Action 이 아닌 행은 기존 JSON truncate + 클릭으로 모달 미리보기 유지 - 모달에서 Action 이 아닌 타입의 raw JSON payload 입력 영역을 완전 제거 — 사용 안 되는 payload zod schema, defaultPayload, register, onSubmit 의 JSON.parse 분기까지 정리 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
다른 어드민 페이지(개념 태그, 문제 등) 들의 표준 레이아웃에 맞춰 4개 라우트 모두 Header + max-w-7xl 컨테이너를 적용한다. - min-h-screen bg-gray-50 outer + Header(title + primary action) + mx-auto max-w-7xl px-8 py-8 wrapper 패턴 적용 - 페이지 내부의 inline "행 추가" Button 은 Header.Button 으로 이동 (개념 노드 / 개념 그래프 / 액션 그래프). 타입 관리는 단일 primary action 이 없어 Header children 만 비워서 호출. - 노드 시트의 payload 셀 예시/포인팅 라벨을 monospace 칩 (border + bg-gray-100) 로 명확한 key 시각 분리, max-w 360px 로 조정 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a7408fd580
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return target?.description && target.description.length > 0 | ||
| ? target.description |
There was a problem hiding this comment.
Convert legacy node descriptions to editor JSON before load
This passes target.description straight into EditorField, but EditorField initializes via parseEditorContent (JSON parse) and falls back to an empty doc on invalid JSON. Any existing node description stored as plain text (legacy format) will render as empty in the modal and can be silently erased on save. Please normalize legacy text into a valid TipTap JSON string before setting the form default.
Useful? React with 👍 / 👎.
| type FormValues = z.infer<typeof formSchema>; | ||
|
|
||
| const extractPayloadEditorString = (raw: unknown): string => { | ||
| if (typeof raw === 'string' && raw.length > 0) return raw; |
There was a problem hiding this comment.
Normalize legacy payload strings before initializing editors
When payload.example / payload.pointingExample are plain strings, this helper returns them unchanged. Those values are then fed into EditorField, which expects serialized editor JSON and falls back to an empty doc for non-JSON strings. Editing an action node with legacy plain-text payload values will therefore open blank fields and overwrite existing data on save.
Useful? React with 👍 / 👎.
Summary
신규 BE API (
/api/admin/concept/graph/*) 위에 시트형 관리자 페이지를 추가합니다. 기존/concept-tags레거시 페이지는 그대로 유지되며, GNB 에 "개념 그래프" 가 문제관리·Q&A 와 동등한 최상위 섹션으로 추가되어 개념 노드 / 개념 그래프 / 액션 그래프 / 타입 관리 4 페이지로 분리됩니다.Linear
Changes
시트 페이지 (4개 라우트)
/concept-graph/node): 이름/설명/타입 필터, 3축 정렬(NAME/NODE_TYPE/DESCRIPTION), TipTap 에디터 기반 description, payload(JSON) 미리보기, 행 추가/수정/삭제/concept-graph/edge— 기존 "개념 엣지" 라벨에서 변경): from/관계/to 필터·정렬,NodeSearchSelect(검색·드롭다운 + initialNode fallback + excludeIds + nodeTypeId 필터), from ≠ to 검증/concept-graph/action-edge): BE 가 보내준 columns(code오름차순) 기준 동적 컬럼, 셀 클릭 → 우측 사이드 패널(CellEditPanel)에서 다중 노드 검색·선택 후PUT /sheet/action-edge/cell일괄 교체, 미저장 변경 시TwoButtonModalTemplate닫힘 confirm, 행 삭제는 모든 셀 비우기로 처리/concept-graph/types): 노드/엣지/액션엣지 타입 3종 시트형 CRUD, react-hook-form + zod 검증Description / Payload 에디터 통합
description을 TipTap 기반 에디터(@repo/pointer-editor-v2) 로 전환 —problem.content와 동일한 react-hook-form Controller 패턴 (EditorField재사용). 빈 doc 은 BE 로undefined전송, plain-text 레거시 데이터는parseEditorContentfallbackdescription컬럼은InlineProblemViewer(maxLine=2) 로 렌더 — KaTeX 자동 + 라인 클램핑payload는 raw JSON textarea 대신 두 개의 에디터 필드 (예시,포인팅 예시) 로 입력 →payload.example/payload.pointingExample키에 매핑되어 저장·로드. 비-Action 타입은 payload 입력 영역 자체를 노출하지 않음InlineProblemViewer(maxLine=1) 로 두 키 모두 인라인 렌더 (mono key chip + value 형태로 시각 분리)액션 노드 행 추가 (
AddActionRowModal)getNodeType()결과에서code === 'Action'매칭으로 nodeType 을 자동 결정 →NodeSearchSelect의nodeTypeId로 전달해 후보를 액션 타입으로만 한정Action코드의 nodeType 이 BE 에 없으면 amber 경고 배너 + 등록 버튼 disabled (운영자는 "타입 관리" 탭에서 먼저 생성해야 함)GNB / 페이지 layout
SectionTitle) 으로 분리, 4 NavItem 으로 펼침 (Circle/Network/Activity/Settings아이콘)<Header>+mx-auto max-w-7xl px-8 py-8레이아웃 적용. 페이지 내부의 inline "행 추가" 버튼은Header.Button으로 이동ConceptGraphTabs제거 — GNB sub-menu 와 역할 중복text-xsinline pill (Tag 컴포넌트 대신)모달 / 컴포넌트 폭
EditConceptNodeModal폭560px→860px(에디터 두 필드 공간 확보)인프라
apps/admin/src/apis/controller/conceptGraph/에 24개 openapi-react-query wrapper (sheet 조회 3종, 노드/엣지/액션엣지 단건 CRUD, 타입 코드 CRUD 3종, 셀 일괄 교체 PUT)SheetTable,PaginationControls,SearchFilterBar,RowActions,NodeSearchSelect,CellEditPanel외 모달 4종 (apps/admin/src/components/conceptGraph/)pnpm openapi→apps/admin/src/types/api/schema.d.ts갱신useInvalidate훅:invalidateConceptGraphSheets/Nodes/Edges/ActionEdges/Types5개 메서드 추가ConceptNodeSheetSearchOptions,ConceptEdgeSheetSearchOptions,ActionGraphSheetSearchOptions(paths[...]추출)Testing
pnpm ci:lint— 0 errors / 0 warningspnpm format:check— All matched files use Prettier code stylepnpm ci:typecheck— 5/5 tasks successfulpnpm ci:build— admin 빌드 성공ci/claude-review/ Vercel 모두 green예시+포인팅 예시두 에디터로 전환, 저장 후 시트 payload 셀에 인라인 렌더Action코드 nodeType 없을 때 disabled 동작Risk / Impact
/concept-tags페이지·API·컴포넌트는 손대지 않음.code === 'Action'(대소문자 정확히 일치) 가정 — BE 데이터에서 코드가 변경되면 amber 경고 배너로 fallback 되며 사용자가 진행 불가. 운영 정책상 코드가 다른 값이면 FE 상수도 함께 수정 필요.payload의example/pointingExample키 컨벤션도 BE 와 함께 합의된 명명 — 변경 시 FE 상수 동기화 필요.description이 BE 컨벤션상 TipTap JSON 문자열로 전환됨 — 레거시 plain text 데이터가 있다면 BE 마이그레이션 또는 FE fallback (parseContent) 의 모니터링이 필요.Screenshots / Video
수동 QA 시 첨부 예정 — GNB "개념 그래프" 섹션 / 노드 시트 (description 인라인, payload key chip) / Action 타입 모달 (예시/포인팅 예시 에디터) / 액션 그래프 pivot + 사이드 패널 / 타입 관리 캡처.
🤖 Generated with Claude Code